package moe.server;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import moe.config.Configuration;
import moe.dao.pool.ConnectionManager;

import org.apache.http.HttpException;
import org.apache.http.HttpServerConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;

public class MoeServer {
	private static final Logger log = Logger.getLogger(MoeServer.class.getName());
	private static final String SERVER_NAME = "MoeServer";

	private static class WorkerThread implements Runnable {

		private final HttpService httpservice;
		private final HttpServerConnection conn;

		private final static AtomicInteger count = new AtomicInteger();

		public WorkerThread(final HttpService httpservice, final HttpServerConnection conn) {
			super();
			this.httpservice = httpservice;
			this.conn = conn;
		}

		public void run() {
			if (log.isLoggable(Level.FINER)) {
				log.finer("New request, count: " + count.incrementAndGet());
			}
			HttpContext context = new BasicHttpContext(null);
			try {
				while (!Thread.interrupted() && this.conn.isOpen()) {
					this.httpservice.handleRequest(this.conn, context);
					/*
					 * short link
					 */
					// this.conn.flush();
					// this.conn.close();
				}
				/*} catch (ConnectionClosedException ex) {
					if (log.isLoggable(Level.FINER)) {
						log.finer("Client closed connection");
					}*/
			} catch (IOException ex) {
				if (!ex.getMessage().contains("Read timed out")) {
					log.warning("I/O error: " + ex.getMessage());
				}
			} catch (HttpException ex) {
				log.warning("Unrecoverable HTTP protocol violation: " + ex.getMessage());
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					this.conn.shutdown();
				} catch (IOException e) {
					// ignore
				}
			}
		}

	}

	private final static int corePoolSize = 1;
	private final static int maximumPoolSize = Integer.MAX_VALUE;
	private final static int keepAliveTime = 5 * 60;

	private final ServerSocket serversocket;
	private final HttpParams params;
	private final HttpService httpService;
	private final ExecutorService threadPool = new ThreadPoolExecutor(corePoolSize,
			maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
			new ThreadFactory() {
				private int index = 0;

				public Thread newThread(Runnable r) {
					Thread t = new Thread(r, "Request Handler Thread-" + index++);
					t.setDaemon(true);
					return t;
				}
			});
	private boolean isRun = true;

	public MoeServer() {
		int port = Configuration.getPort();
		try {
			this.serversocket = new ServerSocket(port);
		} catch (IOException e) {
			log.severe("I/O error: " + e.getMessage());
			throw new RuntimeException(e);
		}
		this.params = new SyncBasicHttpParams();
		this.params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 100000)
				.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
				.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
				.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
				.setParameter(CoreProtocolPNames.ORIGIN_SERVER, SERVER_NAME);

		// Set up the HTTP protocol processor
		// HttpProcessor httpproc =
		// new ImmutableHttpProcessor(new HttpResponseInterceptor[] { new ResponseDate(),
		// new ResponseServer(), new ResponseContent(), new ResponseConnControl() });
		BasicHttpProcessor httpproc = new BasicHttpProcessor();

		HttpLogInterceptor httpLogInterceptor = new HttpLogInterceptor();
		httpproc.addRequestInterceptor(httpLogInterceptor);
		httpproc.addRequestInterceptor(new HttpAuthenticateInterceptor());
		httpproc.addRequestInterceptor(new HttpSessionInterceptor());
		httpproc.addRequestInterceptor(new ImageCacheInterceptor());

		httpproc.addResponseInterceptor(new ResponseDate());
		httpproc.addResponseInterceptor(new ResponseServer());
		httpproc.addResponseInterceptor(new ResponseContent());
		httpproc.addResponseInterceptor(new ResponseConnControl());
		httpproc.addResponseInterceptor(httpLogInterceptor);

		// Set up request handlers
		HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
		reqistry.register("/json", new JsonHandler());
		reqistry.register("*", new CacheableResourcesHandler());

		// Set up the HTTP service
		this.httpService =
				new MoeHttpService(
						httpproc,
						new DefaultConnectionReuseStrategy(),
						new DefaultHttpResponseFactory(),
						reqistry,
						this.params);
	}

	public void startup() {
		while (!Thread.interrupted() && isRun) {
			try {
				// Set up HTTP connection
				Socket socket = this.serversocket.accept();
				DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
				if (log.isLoggable(Level.FINER)) {
					log.finer("Incoming connection from " + socket.getInetAddress());
				}
				conn.bind(socket, this.params);

				// Start worker thread
				threadPool.submit(new WorkerThread(this.httpService, conn));
			} catch (InterruptedIOException ex) {
				break;
			} catch (IOException e) {
				log.severe("I/O error initialising connection thread: " + e.getMessage());
				break;
			}
		}
	}

	public void shutdown() {
		isRun = false;
		threadPool.shutdownNow();
		try {
			this.serversocket.close();
		} catch (IOException e) {
			// ignore
		}
		ConnectionManager.closeAll();
	}
}
